package ECC;

import java.math.*;

public class Point {
	
	// Guarda la x, la y y la curva.
	private BigInteger x;
	private BigInteger y;
	private EllipticCurve curve;
	
	// Condición: (myX, myY) must lie on the curve c. I don't check that here!!!
	public Point(EllipticCurve c, BigInteger myX, BigInteger myY) {
		x = myX;
		y = myY;
		curve = c;
	}
	
	// Copia del constructor.
	public Point(Point copy) {
		x = new BigInteger(copy.x.toString());
		y = new BigInteger(copy.y.toString());
		curve = new EllipticCurve(copy.curve);
	}
	
	// Returna 0. 
	public Point(EllipticCurve c) {
		curve = c;
		x = BigInteger.ZERO;
		y = BigInteger.ZERO;
	}
	
	// Cada uno de los componentes debe ser iguales.
	public boolean equals(Point other) {
		return x.equals(other.x) && y.equals(other.y) && curve.equals(other.curve);
	}
	
	// Devuelve true si y sólo si otro es el reflejo de este punto sobre la línea y = p / 2 (división real)
	public boolean mirror(Point other) {
		return x.equals(other.x) && curve.equals(other.curve) && y.equals(other.curve.getP().subtract(other.y));
	}
	
	// Devuelve el negativo de este punto, que es su espejo.
	public Point negate() {

		BigInteger newY = curve.getP().subtract(y);
		return new Point(curve, x, newY);
	}
	
        public BigInteger acces(Point punto){
            BigInteger x = punto.x;
            return x;
        }
        
	// Añade esto a otro y devuelve la respuesta, utilizando las fórmulas en Stallings (5 ª edición)
	public Point add(Point other) {
		
		// No puede añadir puntos a diferentes curvas.
		if (!curve.equals(other.curve)) return null;
		
		if (this.equals(other)) {
			
			// Necesitamos éstos para calcular lambda.
			BigInteger three = new BigInteger("3");
			BigInteger two = new BigInteger("2");
			BigInteger temp = new BigInteger(x.toString());
			
			// Dividimos el cálculo de lambda en todos estos pasos
			BigInteger lambda = temp.modPow(two, curve.getP());
			lambda = three.multiply(lambda);
			lambda = lambda.add(curve.getA());
			BigInteger den = two.multiply(y);
			lambda = lambda.multiply(den.modInverse(curve.getP()));
			
			// Una vez que tenemos lambda, las metemos en estas ecuaciones.
			BigInteger newX = lambda.multiply(lambda).subtract(x).subtract(x).mod(curve.getP());
			BigInteger newY = (lambda.multiply(x.subtract(newX))).subtract(y).mod(curve.getP());
			return new Point(curve, newX, newY);
			
		}
		
		// Returna el origen.
		else if (this.mirror(other)) {
			return new Point(curve);
		}
		
		// Caso estándar.
		else {
			
			
			BigInteger three = new BigInteger("3");
			BigInteger two = new BigInteger("2");
			BigInteger temp = new BigInteger(x.toString());
                        
			
			// Lambda es un poco más fácil de calcular aquí.
			BigInteger lambda = other.y.subtract(y);
			BigInteger den = other.x.subtract(x);
			lambda = lambda.multiply(den.modInverse(curve.getP()));
			
			// Este cálculo es más o menos el mismo que el anterior.
			BigInteger newX = lambda.multiply(lambda).subtract(x).subtract(other.x).mod(curve.getP());
			BigInteger newY = (lambda.multiply(x.subtract(newX))).subtract(y).mod(curve.getP());
			return new Point(curve, newX, newY);			
			
		}
	}
	
	// La resta solo está agregando el negativo.
	public Point subtract(Point other) {
		other = other.negate();
		return this.add(other);
	}
	
	// Utilizamos "multiplicación rápida" para multiplicar este punto por el factor.
	public Point multiply(BigInteger factor) {
		
		BigInteger two = new BigInteger("2");
		
		// Casos base.
		if (factor.equals(BigInteger.ONE))
			return new Point(this);
		if (factor.equals(two))
			return this.add(this);
		
		
		if (factor.mod(two).equals(BigInteger.ZERO)) {
			Point sqrt = multiply(factor.divide(two));
			return sqrt.add(sqrt);
		}
		
		// Esta llamada recursiva conducirá a uno.
		else {
			factor = factor.subtract(BigInteger.ONE);
			return this.add(multiply(factor));
		}
		
	}
	
	public String toString() {
		return "(" + x +", "+y+")";
	}
	
	public static void curve(String[] args) {
		
		
		EllipticCurve myCurve = new EllipticCurve(new BigInteger("23"), new BigInteger("1"), new BigInteger("1"));
		Point p = new Point(myCurve, new BigInteger("3"), new BigInteger("10"));
		Point q = new Point(myCurve, new BigInteger("9"), new BigInteger("7"));
		
		// P + Q
		Point pPlusq = p.add(q);
		System.out.println(p+" + "+q+" = "+pPlusq);
		
		// 2P
		Point twoP = p.add(p);
		System.out.println("2* "+p+" = "+twoP);
		
		// Test multiplicación
		Point fourP = p.multiply(new BigInteger("4"));
		System.out.println("Four times p = "+fourP);
		
		Point checkFourP = twoP.add(twoP);
		System.out.println("check Four times p = "+checkFourP);
		
	}
}
